package by.bsu.wp.latextool.parser;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import by.bsu.wp.latextool.builder.NFRSProblemBuilder;
import by.bsu.wp.latextool.exception.NFRSProblemInconsistantException;
import by.bsu.wp.latextool.exception.ParsingException;
import by.bsu.wp.latextool.model.NotFullRankSystemProblem;

public class NFRSProblemParser {

	private static Pattern pat(String regex) {
		return Pattern.compile(regex);
	}

	private static String wrap(String rawString) {
		return "\\/\\*" + rawString + "\\*\\/";
	};

	private static final Pattern NUMBER_OF_VERTICES_PATTERN = pat(wrap("\\|I\\|") + "(\\d+)");
	private static final Pattern NUMBER_OF_EDGES_PATTERN = pat(wrap("\\|U\\|") + "(\\d+)");
	private static final Pattern NUMBER_OF_PRODUCTS_PATTERN = pat(wrap("\\|K\\|") + "(\\d+)");
	private static final Pattern NUMBER_OF_BASE_EQUATIONS_PATTERN = pat(wrap("q") + "(\\d+)");

	private static final Pattern PRODUCT_FLOW_NET_PATTERN = pat(wrap("widetilde\\{U\\}\\^(\\d+)"));
	private static final Pattern ZERO_EDGES_PATTERN = pat(wrap("widetilde\\{U\\}_(\\d+)"));
	private static final Pattern LAMBDA_PATTERN = pat(wrap("lambda_(\\d+)(\\d+)\\^(\\d+)(\\d+)") + "(-?\\d+)");
	private static final Pattern BALANCE_PATTERN = pat(wrap("a_(\\d+)\\^(\\d+)") + "(-?\\d+)");
	private static final Pattern BASE_EQUATION_TOTAL_PATTERN = pat(wrap("alpha_(\\d+)") + "(-?\\d+)");
	private static final Pattern ZERO_EDGES_EQUATION_TOTAL_PATTERN = pat(wrap("z_(\\d+)(\\d+)") + "(-?\\d+)");

	private static final Pattern EDGE_PATTERN = pat("\\{(\\d+),(\\d+)\\}");
	private static final Pattern EDGE_MARK_PATTERN = pat(wrap("\\{(\\d+),(\\d+)\\}") + "(\\d+)");

	public NotFullRankSystemProblem parse(Reader reader) throws ParsingException {
		NFRSProblemBuilder problemBuilder = new NFRSProblemBuilder();
		BufferedReader br = new BufferedReader(reader);
		Matcher matcher;
		try {

			matcher = NUMBER_OF_VERTICES_PATTERN.matcher(br.readLine()); matcher.find();
			int numberOfVertices = Integer.parseInt(matcher.group(1));

			matcher = NUMBER_OF_EDGES_PATTERN.matcher(br.readLine()); matcher.find();
			int numberOfEdges = Integer.parseInt(matcher.group(1));

			int sourceVertex, targetVertex;
			for (int i = 0; i < numberOfEdges; i++) {
				matcher = EDGE_PATTERN.matcher(br.readLine()); matcher.find();
				sourceVertex = Integer.parseInt(matcher.group(1));
				targetVertex = Integer.parseInt(matcher.group(2));
				problemBuilder.addEdge(sourceVertex, targetVertex);
			}

			matcher = NUMBER_OF_PRODUCTS_PATTERN.matcher(br.readLine()); matcher.find();
			int numberOfProducts = Integer.parseInt(matcher.group(1));

			problemBuilder.createProductFlowNets(numberOfProducts);

			int product;
			for (int i = 0; i < numberOfProducts; i++) {
				matcher = PRODUCT_FLOW_NET_PATTERN.matcher(br.readLine()); matcher.find();
				product = Integer.parseInt(matcher.group(1));

				for (int j = 0; j < numberOfEdges; j++) {
					matcher = EDGE_MARK_PATTERN.matcher(br.readLine()); matcher.find();
					sourceVertex = Integer.parseInt(matcher.group(1));
					targetVertex = Integer.parseInt(matcher.group(2));
					int marker = Integer.parseInt(matcher.group(3));

					if (marker == 1) {
						problemBuilder.addEdgeForProduct(product, sourceVertex, targetVertex);
					}
				}
			}

			matcher = ZERO_EDGES_PATTERN.matcher(br.readLine()); matcher.find();
			if (matcher.matches() && Integer.parseInt(matcher.group(1)) == 0) {
				for (int j = 0; j < numberOfEdges; j++) {
					matcher = EDGE_MARK_PATTERN.matcher(br.readLine()); matcher.find();
					sourceVertex = Integer.parseInt(matcher.group(1));
					targetVertex = Integer.parseInt(matcher.group(2));
					int marker = Integer.parseInt(matcher.group(3));

					if (marker == 1) {
						problemBuilder.markZeroEdge(sourceVertex, targetVertex);
					}
				}
			}
			
			matcher = NUMBER_OF_BASE_EQUATIONS_PATTERN.matcher(br.readLine()); matcher.find();
			int numberOfBaseEquations = Integer.parseInt(matcher.group(1));

			problemBuilder.setNumberOfBaseEquations(numberOfBaseEquations);
			
			String line = br.readLine();
			
			matcher = LAMBDA_PATTERN.matcher(line);
			while (matcher.find()) {
				sourceVertex = Integer.parseInt(matcher.group(1));
				targetVertex = Integer.parseInt(matcher.group(2));
				product = Integer.parseInt(matcher.group(3));
				int p = Integer.parseInt(matcher.group(4));
				int lambda = Integer.parseInt(matcher.group(5));
				
				problemBuilder.setLambdaForProductEdge(product, sourceVertex, targetVertex, p, lambda);
				
				line = br.readLine();
				matcher = LAMBDA_PATTERN.matcher(line);
			}
			
			matcher = BALANCE_PATTERN.matcher(line); 
			while (matcher.find()) {
				int vertex = Integer.parseInt(matcher.group(1));
				product = Integer.parseInt(matcher.group(2));
				int balance = Integer.parseInt(matcher.group(3));
				
				problemBuilder.setVertexBalanceForProduct(product, vertex, balance);
				
				line = br.readLine();
				matcher = BALANCE_PATTERN.matcher(line);
			}
			
			matcher = BASE_EQUATION_TOTAL_PATTERN.matcher(line);
			while (matcher.find()) {
				int p = Integer.parseInt(matcher.group(1));
				int total = Integer.parseInt(matcher.group(2));
				
				problemBuilder.setTotalForBaseEquation(p, total);
				
				line = br.readLine();
				matcher = BASE_EQUATION_TOTAL_PATTERN.matcher(line);
			}
			
			matcher = ZERO_EDGES_EQUATION_TOTAL_PATTERN.matcher(line);
			while (matcher.find()) {
				sourceVertex = Integer.parseInt(matcher.group(1));
				targetVertex = Integer.parseInt(matcher.group(2));
				int z = Integer.parseInt(matcher.group(3));
				
				problemBuilder.setTotalForZeroEdge(sourceVertex, targetVertex, z);
				
				line = br.readLine();
				if (line == null) {
					break;
				}
				matcher = ZERO_EDGES_EQUATION_TOTAL_PATTERN.matcher(line);
			}

		} catch (IOException | NFRSProblemInconsistantException exception) {
			throw new ParsingException(exception);
		}

		return problemBuilder.getProblem();
	}

}
